#import "BusDirectoryManager.h"

// Singleton instance of BusDirectoryManager
static BusDirectoryManager* sharedBusDirectoryManager = nil;
// Persistent collection of SFU Business directory contacts
static PersistentDataCollection* storedContacts;
// The operation queue for caching updated directory contacts
static NSOperationQueue* cacheQueue; 

/**
 * A BusDirectoryManager is an object representing the complete
 * management of business directory functionality.
 *
 * A MapBuildingSearchDelegate must be set to respond to requests
 * to view an office location on the map.
 *
 * A BusDirectoryDelegate must be set to respond to events from
 * within the BusDirectoryManager.
 *
 * @author      James Lin
 * @version     2.3.1
 * @since       1.0.7
 * @see         ABUnknownPersonViewControllerDelegate
 * @see         UIAlertViewDelegate
 * @see         MapBuildingSearchDelegate
 * @see         BusDirectoryDelegate
 */
@implementation BusDirectoryManager

@synthesize contacts;
@synthesize indices;

/**
 * Retrieve the shared instance of the business directory manager.
 *
 * @return  the shared instance of BusDirectoryManager
 */
+ (BusDirectoryManager*)sharedBusDirectoryManager {
	@synchronized(self) {
		if (sharedBusDirectoryManager == nil) {
			sharedBusDirectoryManager = [[BusDirectoryManager alloc] init];
			storedContacts = [[PersistentDataCollection alloc] initWithFile:BUSDIRECTORY_FILENAME 
														  usingSubdirectory:BUSDIR_SUBDIRECTORY];
			cacheQueue = [[NSOperationQueue alloc] init];
		}
		return sharedBusDirectoryManager;
	}
}

/**
 * Allocate the business directory manager.
 *
 * @return  the newly allocated shared instance of BusDirectoryManager
 */
+ (id) alloc {
	@synchronized(self) {
		sharedBusDirectoryManager = [super alloc];
		return sharedBusDirectoryManager;
	}
}

/**
 * Retrieve a copy of the business directory manager.
 *
 * @return  the shared instance of BusDirectoryManager
 */
+ (id) copy {
	@synchronized(self) {
		return sharedBusDirectoryManager;
	}
}

/**
 * Helper method to create a standardized record given an SFU contact.
 *
 * @param contact  the SFU contact from which to create a record
 * @return         the standardized record of the SFU contact
 */
- (ABRecordRef)createRecordWithContact:(SFUContact*)contact {
	// Create a new record
	ABRecordRef record = ABPersonCreate();
	CFErrorRef error = NULL;
	
	// Add first name and last name
	ABRecordSetValue(record, kABPersonFirstNameProperty, contact.firstName, &error);
	ABRecordSetValue(record, kABPersonLastNameProperty, contact.lastName, &error);
	
	// Add job title if it exists
	if ([contact.jobTitle length] > 0) {
		ABRecordSetValue(record, kABPersonJobTitleProperty, contact.jobTitle, &error);
	}
	
	// Add email address
	ABMutableMultiValueRef email = ABMultiValueCreateMutable(kABMultiStringPropertyType);
	ABMultiValueAddValueAndLabel(email, contact.email, (CFStringRef)@"Email", NULL);
	ABRecordSetValue(record, kABPersonEmailProperty, email, &error);
	CFRelease(email);
	
	// Add phone numbers if they exist
	ABMutableMultiValueRef phone = ABMultiValueCreateMutable(kABMultiStringPropertyType);
	for (NSString* phoneKey in [contact.phoneDict allKeys]) {
		ABMultiValueAddValueAndLabel(phone, [contact.phoneDict objectForKey:phoneKey], 
									 (CFStringRef)phoneKey, NULL);
	}
	ABRecordSetValue(record, kABPersonPhoneProperty, phone, nil);
	CFRelease(phone);
	
	// Add room numbers if they exist
	ABMutableMultiValueRef room = ABMultiValueCreateMutable(kABDictionaryPropertyType);
	for (NSString* roomKey in [contact.roomDict allKeys]) {
		NSMutableDictionary* addrDict = [NSMutableDictionary dictionary];
		[addrDict setObject:[contact.roomDict objectForKey:roomKey] 
					 forKey:(NSString*)kABPersonAddressStreetKey];
		ABMultiValueAddValueAndLabel(room, addrDict, 
									 (CFStringRef)roomKey, NULL);
	}
	ABRecordSetValue(record, kABPersonAddressProperty, room, nil);
	CFRelease(room);
	
	// Add about URL if it exists
	ABMutableMultiValueRef url = ABMultiValueCreateMutable(kABMultiStringPropertyType);
	if ([contact.aboutURL length] > 0) {
		ABMultiValueAddValueAndLabel(url, contact.aboutURL, (CFStringRef)@"About", NULL);
	}
	ABRecordSetValue(record, kABPersonURLProperty, url, nil);
	CFRelease(url);
	
	return record;
}

/**
 * Display the directory address book in a new view.
 *
 * Pushes this view onto the navigation controller stack of the parent
 * view. Upon tapping the back button, the view is automatically 
 * popped off the top of the navigation stack.
 *
 * @param navController  the navigation controller of the parent view
 * @param theDelegate    delegate handling building search requests
 */
- (void)displayWithNavController:(UINavigationController*)navController 
						delegate:(id<MapBuildingSearchDelegate>)theDelegate {
	if (!([storedContacts size] > 0)) {
		UIAlertView* alertView = [[[UIAlertView alloc] initWithTitle:@"Directory is empty." 
															 message:@"Would you like to update the directory?" 
															delegate:self 
												   cancelButtonTitle:@"No" 
												   otherButtonTitles:@"Yes", nil] autorelease];
		[alertView show];
	}
	
	buildingDelegate = theDelegate;
	parentNav = navController;
	[parentNav pushViewController:self animated:YES];
}

/**
 * Empty the directory.
 */
- (void)clearDirectory {
	if (storedContacts) {
		[storedContacts release];
	}
	storedContacts = [[PersistentDataCollection alloc] 
					  initWithEmptyFile:BUSDIRECTORY_FILENAME 
					  usingSubdirectory:BUSDIR_SUBDIRECTORY];
}

/**
 * Set the delegate supplying internet connectivity status.
 *
 * @param delegate  the delegate supplying internet connectiviity status
 * @see             ReachabilityDelegate
 */
- (void)setReachabilityDelegate:(id<ReachabilityDelegate>)delegate {
	reachabilityDelegate = delegate;
}

/**
 * Set the directory event delegate.
 *
 * @param theDelegate  the delegate handling directory events
 */
- (void)setDirectoryDelegate:(id<BusDirectoryDelegate>)theDelegate {
	directoryDelegate = theDelegate;
}

/**
 * Update the directory of contacts with a fresh set of data.
 */
- (void)updateDirectory {
	if (reachabilityDelegate && [reachabilityDelegate hasActiveConnection]) {
		[self clearDirectory];
		
		// Initiate the caching in another thread
		DirectoryCacheOperation* dirCacheOp = [[DirectoryCacheOperation alloc] 
											   initWithTargetCollection:storedContacts
											   delegate:directoryDelegate];
		[cacheQueue addOperation:dirCacheOp];
		[dirCacheOp release];
	} else {
		[GraphicsUtilities displayAlertWithTitle:NO_CONNECTIVITY_MSG 
										 message:@"Cannot update directory."];
	}
}

/**
 * Get the number of sections in the table.
 */
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
    return [contacts count];
}

/**
 * Get the number of rows in each section of the table.
 */
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
	return [[contacts objectAtIndex:section] count];
}

/**
 * Get the titles for the sections of the table.
 */
- (NSArray*)sectionIndexTitlesForTableView:(UITableView*)tableView {
	return indices;
}

/**
 * Get the index of the section for a given section title.
 */
- (NSInteger)tableView:(UITableView*)tableView sectionForSectionIndexTitle:(NSString*)title 
			   atIndex:(NSInteger)index {
	return [indices indexOfObject:title];
}

/**
 * Get the title of each section header of the table.
 */
- (NSString*)tableView:(UITableView*)tableView titleForHeaderInSection:(NSInteger)section {
	return [indices objectAtIndex:section];
}

/**
 * Update the cell content.
 */
- (UITableViewCell*)tableView:(UITableView*)tableView 
		cellForRowAtIndexPath:(NSIndexPath*)indexPath {
	// Dequeue previous cells for performance sake
    static NSString* CellIdentifier = @"ContactCellIdentifier";
	UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	
	// Get the contact at the given cell index
	SFUContact* contact = ((SFUContact*)[[contacts objectAtIndex:indexPath.section] 
										 objectAtIndex:indexPath.row]);
	
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
									   reuseIdentifier:CellIdentifier] autorelease];

		// Create the label for the contact name
		UILabel* nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(6, 0, 
																	   VIEW_WIDTH, 
																	   DIR_CELL_HEIGHT)];
		nameLabel.text = [NSString stringWithFormat:@"%@ %@", 
						  contact.lastName, contact.firstName];
		nameLabel.font = [UIFont systemFontOfSize:16.0];
		nameLabel.numberOfLines = 0;
		nameLabel.lineBreakMode = UILineBreakModeWordWrap;
		nameLabel.tag = 1;
		[cell.contentView addSubview:nameLabel];
		[nameLabel release];
		
		// Create the overlayed label for the contact's last name
		UILabel* lastNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, 
																		   VIEW_WIDTH, 
																		   DIR_CELL_HEIGHT)];
		lastNameLabel.text = contact.lastName;
		lastNameLabel.font = [UIFont systemFontOfSize:16.0];
		lastNameLabel.numberOfLines = 0;
		lastNameLabel.lineBreakMode = UILineBreakModeWordWrap;
		lastNameLabel.tag = 2;
		[cell.contentView addSubview:lastNameLabel];
		[lastNameLabel release];
		
		// Set the background color of the labels
		UIView* cellBackgroundView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
		cellBackgroundView.backgroundColor = [UIColor lightGrayColor];
		cell.backgroundView = cellBackgroundView;
		for (UIView* view in cell.contentView.subviews) {
			view.backgroundColor = [UIColor clearColor];
		}
		
		// Set the background color for the cell when selected
		UIView* selectedCellView = [[[UIView alloc] init] autorelease];
		selectedCellView.backgroundColor = [UIColor darkGrayColor];
		cell.selectedBackgroundView = selectedCellView;
	}

	// Refresh the label content of the cell
	UILabel* tempNameLabel = (UILabel*)[cell viewWithTag:1];
	UILabel* tempLastNameLabel = (UILabel*)[cell viewWithTag:2];
	tempNameLabel.text = [NSString stringWithFormat:@"%@ %@", 
						  contact.lastName, contact.firstName];
	tempLastNameLabel.text = contact.lastName;
	
    return cell;
}

/**
 * Handle the selection of a contact entry.
 *
 * Displays a new view with the specific details of a contact.
 */
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
	[tableView deselectRowAtIndexPath:indexPath animated:YES];
	
	// Get the contact at the given cell index
	SFUContact* contact = [[contacts objectAtIndex:indexPath.section] 
						   objectAtIndex:indexPath.row];
	contactView = [[ABUnknownPersonViewController alloc] init];
	
	// Create a standardized contact record to pass into the template view
	ABRecordRef person = [self createRecordWithContact:contact];
	contactView.displayedPerson = person;
	contactView.allowsActions = YES;
	contactView.allowsAddingToAddressBook = YES;
	contactView.unknownPersonViewDelegate = self;
	contactView.view.backgroundColor = [UIColor grayColor];
	
	// Display the new view with the details of the selected contact
	[parentNav pushViewController:contactView animated:YES];
	CFRelease(person);
}

/**
 * Overriden method.
 *
 * @see ABUnknownPersonViewControllerDelegate
 */
- (void)unknownPersonViewController:(ABUnknownPersonViewController*)unknownPersonView
				 didResolveToPerson:(ABRecordRef)person {
	// Do nothing
}

/**
 * Handle an action request from the contact viewer.
 *
 * @see ABUnknownPersonViewControllerDelegate
 */
- (BOOL)unknownPersonViewController:(ABUnknownPersonViewController*)personViewController 
shouldPerformDefaultActionForPerson:(ABRecordRef)person
						   property:(ABPropertyID)property
						 identifier:(ABMultiValueIdentifier)identifier {
	// Link the view location action to the map building search
	if (property == kABPersonAddressProperty) {
		ABMultiValueRef roomProperty = ABRecordCopyValue(person, property);
		NSDictionary* room = (NSDictionary*)ABMultiValueCopyValueAtIndex(roomProperty, identifier);
		if ([room count] > 0) {
			NSString* roomValue = [[room allValues] objectAtIndex:0];
			Building* loc = [ParsingUtilities parseBuildingFromString:roomValue];
			if (loc != nil) {
				[self searchRequestByBuilding:loc isNew:YES];
				[parentNav popViewControllerAnimated:YES];
			} else {
				[GraphicsUtilities displayAlertWithTitle:@"Location not found or not supported." 
												 message:nil];
			}
		}
		return NO;
	} else if (property == kABPersonEmailProperty) {
		MFMailComposeViewController* mailer = [[MFMailComposeViewController alloc] init];
		mailer.mailComposeDelegate = self;
		
		ABMultiValueRef emailProperty = ABRecordCopyValue(person, property);
		NSString* email = (NSString*)ABMultiValueCopyValueAtIndex(emailProperty, 0);
		
		if ([MFMailComposeViewController canSendMail]) {
			[mailer setToRecipients:[NSArray arrayWithObject:email]];
			[parentNav presentModalViewController:mailer animated:YES];
		}
		
		[mailer release];
		
		return NO;
	} else if (property == kABPersonInstantMessageProperty) {
		MFMessageComposeViewController* messager = [[MFMessageComposeViewController alloc] init];
		messager.messageComposeDelegate = self;
		
		ABMultiValueRef phoneProperty = ABRecordCopyValue(person, property);
		CFIndex index = ABMultiValueGetIndexForIdentifier(phoneProperty, identifier);
		NSString* phone = (NSString*)ABMultiValueCopyValueAtIndex(phoneProperty, index);
		
		if ([MFMessageComposeViewController canSendText]) {
			[messager setRecipients:[NSArray arrayWithObject:phone]];
			[parentNav presentModalViewController:messager animated:YES];
		}
		
		[messager release];
		
		return NO;
	} else if (property == kABPersonURLProperty) {
		ABMultiValueRef urlProperty = ABRecordCopyValue(person, property);
		NSString* url = (NSString*)ABMultiValueCopyValueAtIndex(urlProperty, 0);
		
		NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
		UIWebView* wv = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, VIEW_WIDTH, VIEW_HEIGHT)];
		wv.scalesPageToFit = YES;
		wv.dataDetectorTypes = UIDataDetectorTypeNone;
		
		UIViewController* vc = [[UIViewController alloc] init];
		vc.title = @"About Page";
		
		[vc.view addSubview:wv];
		[wv loadRequest:request];
		//[request autorelease];
		[wv autorelease];
		
		[parentNav pushViewController:vc animated:YES];
		[vc release];
		
		return NO;
	} else {
		return YES;
	}
}

/**
 * Handle a mail composition complete notification from the mail viewer.
 *
 * @see  MFMailComposeViewControllerDelegate
 */
- (void)mailComposeController:(MFMailComposeViewController*)controller 
		  didFinishWithResult:(MFMailComposeResult)result 
						error:(NSError*)error {
	[parentNav dismissModalViewControllerAnimated:YES];
	
	if (result == MFMailComposeResultFailed) {
		[GraphicsUtilities displayAlertWithTitle:@"E-mail failed to send." 
										 message:nil];
	} else if (result == MFMailComposeResultSent) {
		[GraphicsUtilities displayAlertWithTitle:@"E-mail sent." 
										 message:nil];
	}
}

/**
 * Handle a text message composition complete notification from 
 * the message viewer.
 *
 * @see  MFMessageComposeViewControllerDelegate
 */
- (void)messageComposeViewController:(MFMessageComposeViewController*)controller 
				 didFinishWithResult:(MessageComposeResult)result {
	[parentNav dismissModalViewControllerAnimated:YES];
	
	if (result == MessageComposeResultFailed) {
		[GraphicsUtilities displayAlertWithTitle:@"Message failed to send." 
										 message:nil];
	} else if (result == MessageComposeResultSent) {
		[GraphicsUtilities displayAlertWithTitle:@"Message sent." 
										 message:nil];
	}
}

/**
 * Handle a building search request.
 *
 * Passes the delegate responder chain back to caller of this view.
 *
 * @param building  the building of which a search is requested
 * @param isNew     will instruct callee as to what type of search request is occuring
 * @see             MapBuildingSearchDelegate
 */
- (void)searchRequestByBuilding:(Building*)building isNew:(BOOL)isNew{
	[buildingDelegate searchRequestByBuilding:building isNew:YES];
	[parentNav popViewControllerAnimated:NO];
}

/**
 * Helper method to generate the contact list.
 */
- (void)generateContacts {
	self.indices = [NSArray arrayWithObjects:@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",
											 @"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",
											 @"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",nil];
	
	NSMutableArray* tempContacts = [NSMutableArray array];
	
	for (NSString* index in indices) {
		// Create a new dictionary for contacts at this index
		NSMutableArray* indexContacts = [NSMutableArray array];
		
		for (SFUContact* contact in [storedContacts getDataTypes]) {
			if ([contact.lastName hasPrefix:index]) {
				[indexContacts addObject:contact];
			}
		}
		
		[tempContacts addObject:[indexContacts sortedArrayUsingSelector:@selector(compareLastName:)]];
	}
	
	self.contacts = tempContacts;
}

/**
 * Generate the contact list when directory view is shown.
 */
- (void)viewWillAppear:(BOOL)animated {
	if ([contacts count] > 0) {
		[self generateContacts];
		[self.tableView reloadData];
	}
	
	[super viewWillAppear:animated];
}

/**
 * Set up the directory view.
 */
- (void)viewDidLoad {
	self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 
																   VIEW_WIDTH, 
																   VIEW_HEIGHT)
												  style:UITableViewStylePlain];
	[self.tableView setRowHeight:DIR_CELL_HEIGHT];
	[self.view setBackgroundColor:[UIColor lightGrayColor]];
	self.title = @"Business Directory";
	
	[self generateContacts];
    [super viewDidLoad];
}

/**
 * Handle the confirmation to the alert view.
 *
 * @see UIAlertViewDelegate
 */
- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
	switch (buttonIndex) {
		case 0:
			[parentNav popViewControllerAnimated:YES];
			break;
		case 1:
			[self updateDirectory];
			[parentNav popViewControllerAnimated:YES];
			break;
	}
}

/**
 * Deallocate the business directory manager.
 */
- (void)dealloc {
	[indices release];
	indices = nil;
	[contacts release];
	contacts = nil;
	[cacheQueue release];
	cacheQueue = nil;
	[storedContacts release];
	storedContacts = nil;
	[sharedBusDirectoryManager release];
	sharedBusDirectoryManager = nil;
    [super dealloc];
}

@end